SpringMVC 获取请求信息和视图设置相关技术

构建一个 springmvc 简单应用

相关的依赖:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<packaging>war</packaging>

<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
</dependency>
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring6</artifactId>
</dependency>
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>

<build>
<finalName>my-app-${project.version}</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<source>17</source>
<target>17</target>
<compilerArgs>
<arg>-parameters</arg>
</compilerArgs>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>3.3.2</version>
</plugin>
</plugins>
</build>


配置文件的方式

web.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd"
version="6.0">

<!--编码过滤器(必须放在首位)-->
<filter>
<filter-name>CharacterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>forceResponseEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

<!--REST相关请求方法的支持-->
<filter>
<filter-name>HiddenHttpMethodFilter</filter-name>
<filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>HiddenHttpMethodFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

<!--DispatcherServlet-->
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-mvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
<multipart-config>
<location></location><!--临时文件存放路径(默认"")-->
<max-file-size>2097152</max-file-size>
<max-request-size>4194304</max-request-size>
<file-size-threshold>0</file-size-threshold>
</multipart-config>
</servlet>
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<!-- "/" 表示除了 *.jsp 的请求,其他所有请求都可以匹配 -->
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>

spring-mvc.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">

<!--包扫描-->
<context:component-scan base-package="com.demo.controller"/>

<!--核心作用是注册了两个支撑 Spring MVC 运行的灵魂组件:RequestMappingHandlerMapping 和 RequestMappingHandlerAdapter-->
<!--具体包括:数据绑定与类型转换、JSON 自动转换、数据校验、路径匹配支持Ant风格等优化-->
<!--等价于注解开发中的 @Configuration @EnableWebMvc-->
<mvc:annotation-driven />

<!--Thymeleaf 视图解析器-->
<bean id="templateResolver" class="org.thymeleaf.spring6.templateresolver.SpringResourceTemplateResolver">
<property name="order" value="1"/>
<property name="characterEncoding" value="UTF-8" />
<property name="prefix" value="/WEB-INF/templates/" />
<property name="suffix" value=".html" />
<property name="templateMode" value="HTML" />
</bean>
<bean id="templateEngine" class="org.thymeleaf.spring6.SpringTemplateEngine">
<property name="templateResolver" ref="templateResolver" />
</bean>
<bean class="org.thymeleaf.spring6.view.ThymeleafViewResolver">
<property name="templateEngine" ref="templateEngine" />
<property name="characterEncoding" value="UTF-8" />
</bean>

<!--JSP 视图解析器-->
<!--<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/templates/"/>
<property name="suffix" value=".jsp"/>
</bean>-->

<!--静态资源映射-->
<mvc:resources mapping="/static/**" location="/static/" />

<!--文件上传支持-->
<bean id="multipartResolver" class="org.springframework.web.multipart.support.StandardServletMultipartResolver"/>
</beans>

LoginController:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Controller
public class LoginController {

@GetMapping("/login")
public String showLoginPage() {
return "login"; // 自动映射到 /WEB-INF/templates/login.html
}

@PostMapping("/doLogin")
public String doLogin(String username, String password, HttpSession session, Model model) {
if ("admin".equals(username) && "123".equals(password)) {
session.setAttribute("user", username);
return "redirect:/index"; // 重定向
}
model.addAttribute("error", "账号或密码错误");
return "login"; // 转发
}

@GetMapping("/index")
public String index() {
return "index";
}
}

login.html:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>登录 - Owlias Evolution</title>
</head>
<body>
<form th:action="@{/doLogin}" method="post">
<input type="text" name="username" placeholder="用户名" />
<input type="password" name="password" placeholder="密码" />
<button type="submit">登录</button>
</form>
<p th:if="${error}" th:text="${error}" style="color:red;"></p>
</body>
</html>

index.html:

1
2
3
4
5
6
7
8
9
10
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>首页 - Owlias</title>
</head>
<body>
<h1>欢迎回来, <span th:text="${session.user}">访客</span>!</h1>
<a th:href="@{/logout}">安全退出</a>
</body>
</html>


全注解的方式

WebInitializer:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
import jakarta.servlet.Filter;
import org.springframework.web.filter.CharacterEncodingFilter;
import org.springframework.web.filter.HiddenHttpMethodFilter;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;

/**
* 全注解开发,告别 web.xml 的核心启动类
*
* @author KJ
* @description 它会自动被 Servlet 容器(如 Tomcat)发现并加载,其效果完全等同于你在 web.xml 中配置 DispatcherServlet。
*/
public class WebInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

// 1. 指定 Root 容器配置类(存放 Service, DAO 等 Bean)
@Override
protected Class<?>[] getRootConfigClasses() {
// return new Class[] { RootConfig.class };
return null;
}

// 2. 配置过滤器
@Override
protected Filter[] getServletFilters() {
// 2.1. 创建编码过滤器
CharacterEncodingFilter encodingFilter = new CharacterEncodingFilter();
encodingFilter.setEncoding("UTF-8");
encodingFilter.setForceResponseEncoding(true);

// 2.2. 创建 REST 请求方法支持过滤器
HiddenHttpMethodFilter hiddenHttpMethodFilter = new HiddenHttpMethodFilter();

// 2.3. 返回数组的顺序即为 Filter 执行的顺序
// 必须把 encodingFilter 放在第一位,确保请求在被处理前先设置编码
return new Filter[]{encodingFilter, hiddenHttpMethodFilter};
}

// 3. 指定 Servlet 容器配置类(存放 Controller, ViewResolver 等 MVC 相关的 Bean)
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[] { WebMvcConfig.class };
}

// 4. 指定 DispatcherServlet 拦截的路径映射,等价于 <url-pattern>/</url-pattern>
@Override
protected String[] getServletMappings() {
return new String[] { "/" };
}

// 5. 其他自定义注册
@Override
protected void customizeRegistration(jakarta.servlet.ServletRegistration.Dynamic registration) {
// 这里的参数等价于你在 Servlet 章节中学习的 @MultipartConfig
registration.setMultipartConfig(new jakarta.servlet.MultipartConfigElement("", 2097152, 4194304, 0)); // 2MB, 4MB, 阈值 0
}
}

WebMvcConfig:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.thymeleaf.spring6.SpringTemplateEngine;
import org.thymeleaf.spring6.templateresolver.SpringResourceTemplateResolver;
import org.thymeleaf.spring6.view.ThymeleafViewResolver;
import org.thymeleaf.templatemode.TemplateMode;

@Configuration
@EnableWebMvc // 开启 Spring MVC 注解驱动
@ComponentScan("com.demo.controller") // 扫描控制器
public class WebMvcConfig implements WebMvcConfigurer {

// --- Thymeleaf 视图解析器配置 ---
@Bean
public SpringResourceTemplateResolver templateResolver() {
SpringResourceTemplateResolver resolver = new SpringResourceTemplateResolver();
resolver.setPrefix("/WEB-INF/templates/");
resolver.setSuffix(".html");
resolver.setCharacterEncoding("UTF-8");
resolver.setTemplateMode(TemplateMode.HTML); // 明确指定 HTML 模式
return resolver;
}

@Bean
public SpringTemplateEngine templateEngine() {
SpringTemplateEngine engine = new SpringTemplateEngine();
engine.setTemplateResolver(templateResolver());
return engine;
}

@Bean
public ThymeleafViewResolver viewResolver() {
ThymeleafViewResolver vr = new ThymeleafViewResolver();
vr.setTemplateEngine(templateEngine());
vr.setCharacterEncoding("UTF-8");
return vr;
}

// 配置静态资源处理,替代 <mvc:resources />
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/static/**").addResourceLocations("/static/");
}
}

其他配置与xml的方式完全相同。


常用到的获取参数的注解

@RequestMappring

可以用在类上或者方法上,用来设置请求与处理器的匹配关系 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Controller
@RequestMapping("/demo/params")
public class ParamTestController {
//...
}


@RequestMapping(
value = {"/test01", "test01.do", "/tes?01", "/tes*01", "test01/**"}, // or,支持ant风格的路径
method = {RequestMethod.GET, RequestMethod.POST}, // or
params = {"username", "!where", "myReferer=login", "myUserAgent!=IE"}, // and
headers = {"Host=localhost:8080", "Referer"}, // and(用法与 params 完全一样)
consumes = {"application/x-www-form-urlencoded", "application/json"}, // 支持的请求 Content-Type,or
produces = {"text/html;charset=UTF-8", "application/json;charset=UTF-8"} // 设置响应头中的 Content-Type,or
)
public String test01() {
// ...
}


@PathVariable

主要作用是将占位符表示的数据赋值给控制器方法的形参。

1
2
3
4
5
6
@GetMapping("/test02/{id}/{name}")
public String test02(@PathVariable("id") Long id,
@PathVariable(value = "name", required = false) String name) {
System.out.println(id + " " + name);
return "index";
}


@RequestParam

主要作用就是获取请求参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@PostMapping("/doLogin")
public String doLogin(@RequestParam(value = "username") String username,
@RequestParam(value = "password") String password,
@RequestParam(value = "rememberMe", required = false, defaultValue = "false") Boolean rememberMe,
@RequestParam(value = "os", required = false) String[] os,
HttpSession session,
Model model) {
if ("admin".equals(username) && "123".equals(password)) {
session.setAttribute("user", username);
return "redirect:/index"; // 重定向
}
model.addAttribute("error", "账号或密码错误");
return "login"; // 转发
}
1
2
3
4
5
6
7
8
9
10
<form th:action="@{/doLogin}" method="post">
<input type="text" name="username" placeholder="用户名" />
<input type="password" name="password" placeholder="密码" /><br><br>

<input type="checkbox" name="os" value="linux" /> linux
<input type="checkbox" name="os" value="mac" /> mac
<input type="checkbox" name="os" value="windows" /> windows <br><br>

<button type="submit">登录</button>
</form>


@RequestHeader 等注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@GetMapping("/test04")
public String test04(@RequestHeader(value = "host", required = true) String host,
@RequestHeader(value = "referer", required = false, defaultValue = "http://localhost:8080/index") String referer,
@RequestHeader Map<String, Object> cookies, // 请求的所有 header 键值对

@CookieValue(value = "JSESSIONID", required = true) String jsessionId, // 检出 cookie 中的 JSESSIONID 的值
@SessionAttribute("user") Object user,

HttpSession session,
HttpServletRequest request,
HttpServletResponse response) {

System.out.println(host + " " + referer);

Cookie[] requestCookies = request.getCookies();
for (Cookie cookie : requestCookies) {
if ("JSESSIONID".equals(cookie.getName())) {
String value = cookie.getValue();
System.out.println(Objects.equals(jsessionId, value)); // true
}
}

System.out.println(user);
Object userValue = session.getAttribute("user");
System.out.println(Objects.equals(user, userValue)); // true

return "index";
}
1
<a th:href="@{/demo/params/test04(username='admin', myReferer='login')}">参数测试04</a>

使用 servlet 原生API

1
2
3
4
5
6
@GetMapping("/test04")
public String test04(HttpServletRequest request,
HttpServletResponse response,
HttpSession session) {
// ...
}


通过pojo获取参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Data
public class User {
private Long id;
private String username;
private String password;
private Integer gender;
private List<String> hobby;
private Role role;
}

@Data
public class Role {
private Long id;
private String name;
}

@PostMapping("/test05")
public String test05(User user) {
System.out.println(user);
return "index";
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<form th:action="@{/demo/params/test05}" method="post">
<input type="hidden" name="id" value="1" />
<input type="text" name="username" placeholder="用户名" />
<input type="password" name="password" placeholder="密码" /><br><br>

<input type="radio" name="gender" value="1" />
<input type="radio" name="gender" value="0" /><br><br>

<input type="checkbox" name="hobby" value="football" /> football
<input type="checkbox" name="hobby" value="basketball" /> basketball
<input type="checkbox" name="hobby" value="ball" /> ball <br><br>

<input type="hidden" name="role.id" value="1" />
<input type="text" name="role.name" value="common" />

<button type="submit">提交</button>
</form>

<!--或者使用 Thymeleaf 的 th:object 双向绑定-->
<form th:action="@{/demo/params/test05}" th:object="${user}" method="post">
<input type="text" name="username" th:value="${user?.username}" />
<input type="text" name="role.id" th:value="${user?.role?.id}" />
<input type="text" name="role.name" th:value="${user?.role?.name}" />
<button type="submit">提交</button>
</form>


@RequestBody @ResponseBody

@RequestBody:将请求体(JSON/XML)直接绑定到方法参数对象上。常用于 “application/json” 类的请求。

@ResponseBody:告诉 Spring:别找视图解析器了,直接把返回值转成 JSON 发给浏览器。常用于 “application/json” 类的响应。

1
2
3
4
5
6
7
8
9
10
11
/**
* 测试 @RequestBody@ResponseBody
* 这是最常用的简洁方式,默认返回 200 OK
*/
@ResponseBody
@PostMapping("/simpleHandle")
public Emp simpleHandle(@RequestBody Emp emp) {
// 自动将 JSON 转为 Emp 对象,处理后自动转回 JSON
emp.setName(emp.getName() + " (已处理)");
return emp;
}
1
2
3
4
5
6
axios.post('/api/simpleHandle', {
name: '张三',
age: 25
}).then(res => {
console.log("返回数据:", res.data); // {name: "张三 (已处理)", age: 25...}
});


@RequestEntity @ResponseEntity

@RequestEntity:封装了包含请求头、请求体、方法等所有信息的强类型容器。

@ResponseEntity:不仅包含响应体,还能自由设置 HTTP 状态码(如 201, 404)和响应头。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* 测试 RequestEntity 和 ResponseEntity
* 这种方式控制力最强,可以获取请求头并自定义响应状态码
*/
@PostMapping("/check")
public ResponseEntity<String> checkEmp(RequestEntity<Emp> requestEntity) {
// 从 RequestEntity 中获取信息
HttpHeaders headers = requestEntity.getHeaders();
Emp emp = requestEntity.getBody();
String userAgent = headers.getFirst("User-Agent");
System.out.println("来自浏览器: " + userAgent);

// 业务逻辑判断
if (emp != null && emp.getAge() < 18) {
// 返回 400 Bad Request
return ResponseEntity.badRequest().body("未成年人禁止入职");
}

// 返回 201 Created 并设置自定义 Header
return ResponseEntity.status(HttpStatus.CREATED)
.header("Custom-Header", "Owlias")
.body("员工 " + emp.getName() + " 验证通过");
}
1
2
3
4
5
6
7
8
axios.post('/api/check', {
name: '小明',
age: 16
}).catch(error => {
// 这里会捕获到 400 状态码
console.log("状态码:", error.response.status); // 400
console.log("后端消息:", error.response.data); // 未成年人禁止入职
});


文件上传和下载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
// WebInit 中配置限制
@Override
protected void customizeRegistration(ServletRegistration.Dynamic registration) {
// 设置上传限制:单个文件 2MB,总请求 4MB
registration.setMultipartConfig(new MultipartConfigElement(null, 2097152L, 4194304L, 0));
}

// 标准上传接口 (POST)
@PostMapping("/upload")
public R<String> upload(@RequestPart("file") MultipartFile file) {
if (file.isEmpty()) return R.fail("文件不能为空");
try {
// 1. 生成唯一文件名
String originalName = file.getOriginalFilename();
String suffix = originalName.substring(originalName.lastIndexOf("."));
String fileName = UUID.randomUUID().toString() + suffix;

// 2. 检查并创建目录
File destDir = new File(uploadPath);
if (!destDir.exists()) destDir.mkdirs();

// 3. 保存文件
file.transferTo(new File(destDir, fileName));

// 4. 返回文件标识(通常存入数据库,这里返回文件名)
return R.ok(fileName);
} catch (IOException e) {
return R.fail("上传失败:" + e.getMessage());
}
}

// 标准下载接口 (GET + ResponseEntity)
@GetMapping("/download/{fileName}")
public ResponseEntity<Resource> download(@PathVariable String fileName) {
try {
// 1. 加载文件资源
Path path = Paths.get(uploadPath).resolve(fileName);
Resource resource = new UrlResource(path.toUri());

if (!resource.exists()) return ResponseEntity.notFound().build();

// 2. 处理中文文件名(防止下载时文件名乱码)
String downloadName = URLEncoder.encode("员工资料_" + fileName, StandardCharsets.UTF_8);

// 3. 构建响应头
return ResponseEntity.ok()
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + downloadName + "\"")
.body(resource);
} catch (Exception e) {
return ResponseEntity.internalServerError().build();
}
}
1
2
3
4
5
6
7
8
9
<el-upload
class="upload-demo"
action="/api/files/upload"
:on-success="handleUploadSuccess"
:on-error="handleUploadError"
name="file"
:limit="1">
<el-button type="primary">点击上传头像</el-button>
</el-upload>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
const handleUploadSuccess = (response) => {
if (response.code === 0) {
ElementPlus.ElMessage.success('文件上传成功,标识:' + response.data);
// 将返回的文件名绑定到员工表单中
form.avatar = response.data;
} else {
ElementPlus.ElMessage.error(response.msg);
}
};

const downloadFile = (fileName) => {
// 触发下载:浏览器会自动识别 Content-Disposition
window.location.href = `/api/files/download/${fileName}`;
};


处理xml类型的参数

大多数情况下 JSON 是主流,但在对接银行、医疗或老旧 ERP 系统时,XML 依然是不可或缺的数据交换格式。

Spring MVC 处理 XML 的核心原理与 JSON 一致,都是通过 HttpMessageConverter 实现的。不过,处理 XML 需要额外的 “解密工具”:Jackson XML 扩展。

引入必要依赖:

1
2
3
4
5
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
<version>2.15.2</version>
</dependency>

为了让 Spring 知道 XML 标签和 Java 属性的对应关系,我们需要使用 JAXB 或 Jackson XML 注解定义定义业务实体类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
import lombok.Data;

@Data
@JacksonXmlRootElement(localName = "UserRequest") // 定义根节点名称
public class UserXml {

@JacksonXmlProperty(localName = "UserName") // 定义子节点名称
private String name;

@JacksonXmlProperty(localName = "UserAge")
private Integer age;

@JacksonXmlProperty(localName = "DeptName")
private String dept;
}

业务处理方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@RestController
@RequestMapping("/api/xml")
public class XmlController {

/**
* 接收 XML 入参并返回 XML 响应
* consumes: 只有 Header 中 Content-Type 为 application/xml 的请求才进来
* produces: 强制返回 XML 格式
*/
@PostMapping(value = "/user",
consumes = MediaType.APPLICATION_XML_VALUE,
produces = MediaType.APPLICATION_XML_VALUE)
public UserXml handleXml(@RequestBody UserXml user) {
// 业务处理逻辑
System.out.println("收到 XML 数据:" + user);
user.setName(user.getName() + "[已处理]");
return user;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
const xmlData = `
<UserRequest>
<UserName>李四</UserName>
<UserAge>28</UserAge>
<DeptName>测试部</DeptName>
</UserRequest>
`;

axios.post('/api/xml/user', xmlData, {
headers: { 'Content-Type': 'application/xml' }
}).then(res => {
// res.data 会被自动解析为 XML 文档或字符串
console.log(res.data);
});


域对象共享数居

使用 servlet API 共享数居

1
2
3
4
5
6
7
8
9
@GetMapping("/tset06")
public String tset06(HttpServletRequest request, HttpSession session) {
request.setAttribute("request_profile", "hahaha");
session.setAttribute("session_user", "zhangsan");

ServletContext context = request.getServletContext();
context.setAttribute("context_dict_order", "desc");
return "index";
}
1
2
3
4
5
<div>
<p th:text="${request_profile}"></p>
<p th:text="${session.session_user} ?: 'session_user 未找到数据'"></p>
<p th:text="${application.context_dict_order} ?: 'context_dict_order 未找到数据'"></p>
</div>


ModelAndView 共享数据

ModelAndView 可以设置 request 共享(模型)数据,并且设置返回的视图。在 springmvc 中,不管使用哪种方式共享数居,springmvc 底层最终都会将其包装成一个 ModelAndView 对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@GetMapping("/tset07")
public ModelAndView tset07(ModelAndView mv) {
mv.addObject("request_profile", "hehehe");
mv.setViewName("index");
System.out.println(mv.getClass().getName()); // ModelAndView
return mv;
}

// 或者

@GetMapping("/tset07")
public ModelAndView tset07() {
ModelAndView mv = new ModelAndView();
mv.addObject("request_profile", "hehehe");
mv.setViewName("index");
return mv;
}


Model、Map、ModelMap

在 Spring MVC 中,当你把这三者之一写在方法参数时,Spring 传入的通常都是同一个 BindingAwareModelMap 实例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@GetMapping("/tset08")
public String tset08(Model model) {
model.addAttribute("request_profile", "heihei");
System.out.println(model.getClass().getName()); // BindingAwareModelMap
return "index";
}

// 或

@GetMapping("/tset08")
public String tset08(Map<String, Object> map) {
map.put("request_profile", "xixi");
System.out.println(map.getClass().getName()); // BindingAwareModelMap
return "index";
}

// 或

@GetMapping("/tset08")
public String tset08(ModelMap mm) {
mm.addAttribute("request_profile", "hengheng");
System.out.println(model.getClass().getName()); // BindingAwareModelMap
return "index";
}

Map 是根基(JDK 提供)。

ModelMap extends LinkedHashMap<String, Object>,是对 Map 的增强,专门为 Web 开发定制。

Model 是 Spring 为了简化操作而抽象出来的接口。

BindingAwareModelMap extends ModelMap implements Model,是最终干活的 “全能战士”。


SpringMVC 的视图

SpringMVC 中的视图,对应的顶级接口都是 org.springframework.web.servlet.View。视图的作用是渲染数据,将Model中的数据展示给用户。

SpringMVC 视图的种类非常多,默认有两种类型,即转发视图 InternalResourceView 和重定向视图 RedirectView。

当工程中引入了jstl 的依赖,转发视图会自动转换为 JstlView。倘若使用的视图技术为 Thymeleaf,在 springmvc 的配置文件中配置了 Thymeleaf 的视图解析器,那么由此视图解析器解析之后得到的就是 ThymeleafView。

常见的相应视图的写法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@GetMapping("/tset08")
public ModelAndView tset08(ModelAndView mv) {
mv.addObject("request_profile", "hehehe");
mv.setViewName("index");
return mv;
}

@GetMapping("/tset09")
public String tset09() {
// ...
return "forward:/index"; // InternalResourceView,转发跳转,走视图解析器(一次请求)
}

@GetMapping("/tset10")
public String tset10() {
// ...
return "redirect:/login"; // RedirectView,重定向方式跳转,它不会被视图解析器解析(两次请求)
}